I'm doing a social networking project on React
I wanted to replace one component from class - to functional and use hooks, and a global problem appeared:
When I go to a new user, the page displays the status of the previous one
I use useState() hook, debugged everything, but for some reason when a new status component is rendered, it doesn't update
const ProfileStatus = (props) => {
const [edditMode, setEdditMode] = useState(false);
const [status, setValue] = useState(props.status || "Empty");
const onInputChange = (e) => {
setValue(e.target.value);
};
const activateMode = () => {
setEdditMode(true);
};
const deactivateMode = () => {
setEdditMode(false);
props.updateUserStatus(status);
};
I thought the problem was that the container component was still a class component, but by redoing it, nothing has changed
One way to solve this is by using the useEffect hook to trigger an update when props change. You can use the hook to do comparison between current props and previous props, then update status in the state.
Use this as reference and adapt according to your own code.
const ProfileStatus = (props) => {
const [edditMode, setEdditMode] = useState(false);
const [status, setValue] = useState(props.status || "Empty");
useEffect(() => {
setValue(props.status || "Empty");
}, [props.status]);
const onInputChange = (e) => {
setValue(e.target.value);
};
const activateMode = () => {
setEdditMode(true);
};
const deactivateMode = () => {
setEdditMode(false);
props.updateUserStatus(status);
};
Related
I have one customer component. And it's getting called twice. One to create the customer and the second to edit it. When I call this component first-time values are getting initialized and updated correctly. When I render a component a second time for editing customer values are not getting updated via the use effect.
const AddCustomer = (props) => {
console.log("Props",props)
const {customerId} = useParams();
const [customerName, setcustomerName] = useState("");
const [customerKey, setcustomerKey] = useState("");
const [formErrors, setFormErrors] = useState({});
const [apiError, setApiError] = useState(null);
const [isSubmit, setIsSubmit] = useState(false);
const [loadSpinner, setLoadSpinner] = useState(false);
useEffect(() => {
if(props.isEdit) {
populateExistingFormData();
}
if (Object.keys(formErrors).length === 0 && isSubmit) {
callAPI();
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [formErrors, isSubmit, props.isEdit, props]);
const populateExistingFormData = () => {
setCustomerId(props.customerId)
setCustomerName(props.serviceProviderCode)
setKey(props.key)
setIsSubmit(props.isSubmit); //false value from props
setLoadSpinner(props.loadSpinner);
} }
As I am passing submit value as a false from props. But it's set up as true again (value showing from previous component render) and its calls then call API. how can I update the state on component re-render?
I have three places where I'm using the useState hook and I had to write it three times. I want to know if there is a way I could write once instead of having to repeat over again.
const Header = () => {
const [btnOpen, setbtnOpen] = useState(false);
const MobileNavigator = () => {
setbtnOpen(!btnOpen);
}
const [dropDown, setdropDown] = useState(false);
const dropdown = () => {
setdropDown(!dropDown);
}
const [openSearchform, setSearchform] = useState(false);
const formdown = () => {
setSearchform(!openSearchform);
}
you can use HOC(higher order component) for reusing component logic
reference
medium article with example
I have two hooks, one of which depends on the other state. I wrote two patterns to handle it. When useEffect is in custom hooks (and I want to hide useEffect into a custom hook), it causes an infinite loop. Why is that? Thanks.
Infinite loop version
hooks.ts
const useUpdate = (initialValue) => {
[selectedItem, setSelectedItem] = useState(initialValue)
useEffect(() => {
setSelectedItem(initialValue)
}, [initialValue])
const update = () => {...}
return update
}
MyComponent.ts
const MyComponent = () => {
const [selectedItem, setSelectecItem] = useSelectItem()
const update = useUpdate(selectedItem)
return (<div>...</div>)
}
Success version
hooks.ts
const useUpdate = (initialValue) => {
[selectedItem, setSelectedItemInUpdate] = useState(initialValue)
const update = () => {...}
return {update, setSelectedItemInUpdate}
}
MyComponent.tsx
const MyComponent = () => {
const [selectedItem, setSelectecItem] = useSelectItem()
const {update, setSelectedItemInUpdate} = useUpdate(selectedItem)
useEffect(() => {
setSelectedItemInUpdate(selectedItem)
}, [selectedItem])
return (<div>...</div>)
}
Edited (2022/03/07) to answer a comment, contents of custom hooks are shown below. They are still not entire source code, but it can give much more context. useUpdate's return type isn't consistent with the above description.
const useSelectItem = (initialValue: Item[] =[]) => {
// Select and unselect one item.
const [selectedItem, setSelectedItem] = useState<Item[]>(initialValue)
const select = (item: Item) => {
if (selectedItem.at(0) === item) {
// if already selected, unselect it.
selectedItem([])
} else {
selectedItem([item])
}
}
return [selectedItem, select]
}
// useUpdate should operate on a selectedItem
const useUpdate = () => {
// keep shallow copy of selectedItem to edit locally.
const [item, setItem] = useState<Item|undefined>(undefined)
const onChangeName = (event: React.ChangeEvent<HTMLInputElement>) => {
setItem({...item, {[name]: event.target.value}
}
const update = async () => {
// write to firestore provided by Firebase, Google.
await updateDoc(doc(db, 'items', item.id), item)
}
// ... and many other functions to operate on input field.
return {onChangeName, update, and many others}
}
Of course these hooks can be combined into one hook, but I think I have to separate them so that they have a single responsibility.
I think it is happening because of useEffect dependency array. In the infinite loop version of useEffect, initialValue is changing constantly, and it is causing the infinite loop.
Try changing it to the below code.
const useUpdate = (initialValue) => {
[selectedItem, setSelectedItem] = useState(initialValue)
useEffect(() => {
setSelectedItem(initialValue)
//eslint-disable-next-line
}, [])
const update = () => {...}
return update
}
//eslint-disable-next-line won't give you warnings because of empty `dependency array'
As Dharmik Patel's answer stated, the issue is likely caused by the dependency array. Another way you can get around the issue, without disabling eslint is to useMemo.
const useUpdate = (initialValue) => {
const initial = useMemo(() => initialValue, [initialValue])
[selectedItem, setSelectedItem] = useState(initialValue)
useEffect(() => {
setSelectedItem(initial)
}, [initial])
const update = () => {...}
return update
}
The useMemo will ensure that re-renders only occur if the value of initialValue changes. However this article explains an even better way to do this than using useMemo.
I understand that without dependencies you cannot access state inside useEffect. An example as such :
const [state, setState] = React.useState('')
const ws = new WebSocket(`ws://localhost:5000`)
React.useEffect(() => {
ws.onopen = () => {
console.log(state)
},[])
In this case state comes back as empty even though it has changed in other parts of the code. To solve this in principle I should add state as dependency to the useEffect hook. However, this will trigger the listener again and I do not want to have 2 active listeners on my websocket or being forced to close and reopen it again as such :
React.useEffect(() => {
ws.onopen = () => {
console.log(state)
},[state])
What is good practice when it comes to accessing state inside a listener that sits inside useEffect hook?
IF you need to re-render the component when the state changes try this:
const [state, setState] = React.useState('');
const stateRef = React.useRef(state);
React.useEffect(() => {
stateRef.current = state;
}, [state])
const ws = React.useMemo(() => {
const newWS = new WebSocket(`ws://localhost:5000`);
newWS.onopen = () => {
console.log(stateRef.current);
}
return newWS;
}, []);
This way you create the ws only once, and it will use the state reference which will be up to date because of the useEffect.
If you don't need to re-render the component when the state updates you can remove const [state, setState] = React.useState(''); and the useEffect and just update the stateRef like this when you need.
Like this:
const stateRef = React.useRef(null);
const ws = React.useMemo(() => {
const newWS = new WebSocket(`ws://localhost:5000`);
newWS.onopen = () => {
console.log(stateRef.current);
}
return newWS;
}, []);
// Update the state ref when you need:
stateRef.current = newState;
Best Practice get data in listeners.[UPDATED]!
const [socketData , setSocketData] = useState(null);
useEffect(() => {
websocet.open( (data) => {
setSocketData(data);
})
},[])
//second useEffect to check socketData
useEffect(() => {
if(socketData){
// access to data which come from websock et
}
},[socketData])
So I've read these blog posts about using custom hooks to fetch data, so for instance we have a custom hook doing the API call, setting the data, possible errors as well as the spinny isFetching boolean:
export const useFetchTodos = () => {
const [data, setData] = useState();
const [isFetching, setIsFetching] = useState(false);
const [error, setError] = useState();
useEffect(() => {
setIsFetching(true);
axios.get('api/todos')
.then(response => setData(response.data)
.catch(error => setError(error.response.data)
.finally(() => setFetching(false);
}, []);
return {data, isFetching, error};
}
And then at the top level of our component we would just call const { data, error, fetching } = useFetchTodos(); and all great we render our component with all the todos fetched.
The thing I don't understand is how would we send dynamic data / parameters to the hook based on the internal state of the component, without breaking the rules of hooks?
For instance, imagine we have a useFetchTodoById(id) hook defined the same way as the above one, how would we pass that id around? Let's say our TodoList component which renders our Todos is the following:
export const TodoList = (props) => {
const [selectedTodo, setSelectedTodo] = useState();
useEffect(() => {
useFetchTodoById(selectedTodo.id) --> INVALID HOOK CALL, cannot call custom hooks from useEffect,
and also need to call our custom hooks at the "top level" of our component
}, [selectedTodo]);
return (<ul>{props.todos.map(todo => (
<li onClick={() => setSelectedTodo(todo.id)}>{todo.name}</li>)}
</ul>);
}
I know for this specific usecase we could pass our selectedTodo through props and call our useFetchTodoById(props.selectedTodo.id) at the top of our component, but I'm just illustrating the issue with this pattern I ran into, we won't always have the luxury of receiving the dynamic data that we need in the props.
Also -- how would we apply this pattern for POST/PUT/PATCH requests which take dynamic data properties?
You should have a basic useFetch hook the accepts a url, and fetches whenever the url changes:
const useFetch = (url) => {
const [data, setData] = useState();
const [isFetching, setIsFetching] = useState(false);
const [error, setError] = useState();
useEffect(() => {
if(!url) return;
setIsFetching(true);
axios.get(url)
.then(response => setData(response.data))
.catch(error => setError(error.response.data))
.finally(() => setFetching(false));
}, [url]);
return { data, isFetching, error };
};
Now you can create other custom hook from this basic hook:
const useFetchTodos = () => useFetch('api/todos');
And you can also make it respond to dynamic changes:
const useFetchTodoById = id => useFetch(`api/todos/${id}`);
And you can use it in the component, without wrapping it in useEffect:
export const TodoList = (props) => {
const [selectedTodo, setSelectedTodo] = useState();
const { data, isFetching, error } = useFetchTodoById(selectedTodo.id);
return (
<ul>{props.todos.map(todo => (
<li onClick={() => setSelectedTodo(todo.id)}>{todo.name}</li>)}
</ul>
);
};